golangのgit ライブラリ「go-git」を使ってインメモリでgit操作をする
こんにちは。齋藤です。 今日はgolangを使ってgitの操作をやります。
はじめに
golangには pure go implementation な git のライブラリがあります。
今回の記事では、このライブラリを使って git の操作を行います。
今回動かしたいシナリオは以下のようなものです。
- あるリポジトリAを リリースする
- リポジトリAのイベントをhook起点に別のリポジトリBに コミットして PRを送る
今回はリポジトリBに commit して PRを送るに着目して この記事では リポジトリBを clone した後にファイルを変更・commit して push までをやってみます。
なお後々、Webhook などで動かしたいので AWS Lambda の上で動かしてみます。 gitの操作は全てインメモリで行います。
準備
今回はAWS Lambdaで実行することを考えているので アクセストークンを利用してリポジトリを clone します。
そのため、GitHubからなんらかの方法でrepo scopeのトークンを取得しておいてください。 なお、go-gitの場合、実行環境でGitHub に設定しているssh鍵がある場合は不要です。
lambdaを起動するための cloudformation
本題ではないので、ここは以下のようにさっくり設定しておきます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: golang-lambda-test Parameters: GitHubToken: Type : String Description : Enter github repo scope token. Resources: Function: Type: AWS::Serverless::Function Properties: Handler: handler Runtime: go1.x CodeUri: handler.zip Handler: handler Role: !GetAtt GoOnLambdaIamRole.Arn Timeout: 60 Environment: Variables: GITHUB_ACCESS_TOKEN: !Ref GitHubToken Events: ProxyApiRoot: Type: Api Properties: Path: / Method: ANY GoOnLambdaIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: "sts:AssumeRole" Path: / Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup # ログ出力のためのポリシー - logs:CreateLogStream - logs:PutLogEvents Resource: '*'
サンプルコードとビルド
サンプルコードです。 cloneします。
インメモリでcloneしています。
f := memfs.New() repo, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{ URL: "https://" + userID + ":" + token + "@github.com/<your-id>/<your-repository>.git", ReferenceName: plumbing.ReferenceName("refs/heads/develop"), })
新しいブランチをcheckoutします。
w, err := repo.Worktree() err = w.Checkout(&git.CheckoutOptions{ Create: true, Branch: "feature/test-update", })
リポジトリにあるファイルを書き換えます。
// ファイルを書き換える // f := memfs.New() err = rewriteVersion(f, ur.Version) func rewriteVersion(fs billy.Filesystem, version string) (err error) { file, err := fs.Open(".version") if err != nil { return } b, err := ioutil.ReadAll(file) if err != nil { return } err = file.Close() if err != nil { return } var obj interface{} json.Unmarshal(b, &obj) jsonpointer.Set(obj, "/version", version) rb, err := json.MarshalIndent(obj, "", " ") if err != nil { return } err = fs.Remove(".version") if err != nil { return } file, err = fs.OpenFile(".version", os.O_RDWR|os.O_CREATE, 0666) if err != nil { return } _, err = file.Write(rb) if err != nil { return } _, err = file.Write([]byte("\n")) return }
今度は書き換えたファイルをcommitします。
// Commitする _, err = w.Add(".version") ref := plumbing.ReferenceName("feature/test-update") if err == nil { hash, _ := w.Commit("update version", &git.CommitOptions{ Author: &object.Signature{ Name: "<your-id>", Email: "<your-mail-address>", When: time.Now(), }, }) // ここが何故か必要だったのだけれど調べてない repo.Storer.SetReference(plumbing.NewReferenceFromStrings("feature/test-update", hash.String())) }
コミットしたブランチをpushします。
// Pushする remote, err := repo.Remote("origin") if err == nil { err = remote.Push(&git.PushOptions{ Progress: os.Stdout, RefSpecs: []config.RefSpec{ config.RefSpec(ref + ":" + plumbing.ReferenceName("refs/heads/feature/test-update")), }, }) }
サンプルコード全体はこちらから見れます。
ビルドは以下のコマンドで AWS Lambda向けにビルドしておきます。 今回は簡略化のためにビルドのオプションは渡していません。 必要に応じて適切にビルドのオプションを設定してください。
# zip作っておく。 GOOS=linux GOARCH=amd64 go build -o handler handler.go && zip handler.zip handler
アプリケーションのデプロイ
先ほどビルドしたバイナリがあることを前提にしています。
# s3にcodeをアップロードしつつ templateを出力 aws cloudformation package --template-file template.yml --output-template-file packed.yml --s3-bucket <your-bucket> # 出力されたテンプレートを使って、GitHubのTokenを渡しつつデプロイ aws cloudformation deploy --template-file packed.yml --stack-name lambda-go-test-stack --parameter-overrides "GitHubToken=<token>" --capabilities CAPABILITY_IAM
デプロイしてlambdaを叩いてみます
今回はデプロイはマネージメントコンソールから行いました。 今回のサンプルコードでは、以下のような形で呼び出すことができます。
curl -XPOST https://$REST_API_ID.execute-api.$REGION.amazonaws.com/$STAGE -d '{"version": "from curl"}' # updated version
まとめ
今回は golangとgitのライブラリである、go-gitを使って、基本的なgit操作を行いました。 今回のサンプルコードでは、GitHubにあるprivateリポジトリにあるコードを インメモリで編集してPushする所まで行いました。
ここからPRを作る所まで自動化できると楽しそうですね。
また、最近isomorphic-git という node/browserで動くgitのライブラリもあるので 気になっています。git操作に関するエコシステムが育っていくと楽しそうですね。
今度はここから発展した記事を書く予定でいます。 ではまた次の記事でお会いしましょう。
おまけ: IP制限
# StackにRestApiが一つしかないのでこれで大丈夫 REST_API_ID=$(aws cloudformation describe-stack-resources --stack-name lambda-go-test-stack | jq '.StackResources[] | select(.ResourceType == "AWS::ApiGateway::RestApi") | .PhysicalResourceId' -r) # ポリシードキュメントを `jq ". | tostring" -c` でエスケープ。 JSON=$(cat << EOF | jq ". | tostring" -c { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": [ "arn:aws:execute-api:<>:<your-account-id>:$REST_API_ID/*" ], "Condition" : { "IpAddress": { "aws:SourceIp": [ "<your-ip-address>" ] } } } ] } EOF ) # 設定を更新 aws apigateway update-rest-api --rest-api-id $REST_API_ID --patch-operations op=replace,path=/policy,value=$JSON # 設定を更新後、再デプロイする必要がある。 aws apigateway update-stage --rest-api-id $REST_API_ID --stage-name <your-stage> curl -XPOST https://$REST_API_ID.execute-api.$REGION.amazonaws.com/prod -d '{"version": "from curl"}' # updated version